package me.w70.fps;

import java.applet.Applet;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import me.w70.fps.actor.Player;
import me.w70.fps.constants.Angles;
import me.w70.fps.map.Map;
import me.w70.fps.raycast.Raycaster;
import me.w70.fps.raycast.TrigTableBuilder;
import me.w70.fps.render.GameWindow;

public class FPS extends Applet implements Runnable, KeyListener {
	/*
	 * PROGRAM FLOW
	 * So it's easier to follow, here's the general flow of the program.
	 * 	Initialize FPS
	 * 	|	Create graphics objects for the applet
	 * 	|	Create Angles angles, which sets up the constant angles to be used.
	 * 	|		- Angle values dependant on game window dimensions.
	 * 	|	Create Map map, which takes a hard-coded char array/file and creates a Cell array for the map.
	 * 	|		- Cells will later allow for easier variable-height walls, differing floor textures, etc.
	 * 	|		- Also may help for AI programming later.
	 * 	|	Create GameWindow graphics, which draws the game window.
	 * 	|		- Grabs all images and converts to ImageData objects, which allow much faster rendering.
	 * 	|	Create Raycaster engine, which handles raycasting and distance finding.
	 * 	|	Create Player player, which is the player's data (hp, speed, etc).
	 * 	|		- Also handles wall collision detection.
	 * 	|	Pass some pointers around.
	 * 	|		- Player gets a pointer to GameWindow, so it can access needed methods. NOT USED YET, but passed.
	 * 	|		- GameWindow gets a pointer to Player so it knows where to draw floors + ceilings.
	 * 	|	Create TrigTableBuilder trigtables, which precalculates various tables for values.
	 * 	|		- Raycaster needs triginometry data for calculations.
	 * 	|			- Player needs sin and cos tables for movement calculations.
	 * 	|				- Raycaster passes these tables to Player.
	 * 	V		- GameWindow needs a table to compensate for the "fishbowl" effect and a table for floor drawing.
	 * 	Now we enter the main loop.
	 * 	^|	Map.updateDoors() called every cycle to update the doors opening/closing.
	 * 	||	Player.move(keys) called every cycle to let the player move.
	 * 	||	Raycaster.castRays() called to begin the rendering process.
	 * 	||		- Cast out 320 rays, from Player.angle - 30 to Player.angle + 30
	 * 	||		- Raycaster.castRay() called for each ray
	 * 	||			- Calculates the ray path, pushes traversed cell locations into a list for later.
	 * 	||			- Finds the closest wall along the ray path, passes distance, wall type, etc to...
	 * 	||		- GameWindow.drawwallslice(), which will draw the actual slice of wall.
	 * 	||			- Later, will draw objects too using the list of cell locations from the ray.
	 * 	||			- Calls GameWindow.fillvbuffer() to fill a Color array with proper pixel colors.
	 * 	||			- Then calls GameWindow.rendervbuffer() to actually draw the buffer to the screen.
	 * 	|V	Finally, we sleep for 50 milliseconds so the action doesn't move too fast for a human to handle.
	 * 	|--	Reloop!
	 */
	
	
	private static final long serialVersionUID = 1L;
	
	private static int gamewindowx = (int) (320 * 1.5);
	private static int gamewindowy = (int) (200 * 1.5);
	
	private static long sleeptime = 0;
	
	// Keys
	protected boolean playing = true;
	protected boolean KeyUp = false;
	protected boolean KeyDown = false;
	protected boolean KeyLeft = false;
	protected boolean KeyRight = false;
	protected boolean KeyCtrl = false;
	protected boolean KeyShift = false;
	protected boolean KeyAlt = false;
	protected boolean KeySpace = false;
	// Graphics objects for drawing to the applet
	Graphics appletgraphics;
	Image offscreengraphics;
	// Constants
	public Angles angles;
	// Map for the engine.
	protected Map themap;
	// Graphics engine for drawing to the applet
	protected GameWindow graphics;
	// Main game engine
	public Raycaster engine;
	public Player player;
	protected TrigTableBuilder trigtables;
	protected Thread mainthread;
	
	private boolean usecollisiondetection = true;
	private boolean useimages = true;
	private boolean hardcodedimages = false;
	
	@Override
	public void run() {
		requestFocus();
		addKeyListener(this);
	    while (true) {
	    	gamemainloop();
	    	break;
	    }
	}
	
	public void main(String args[]) {
		new FPS();
	}
	
	public void start() {
		this.setPreferredSize(new Dimension(gamewindowx * 2, gamewindowy * 2));
		offscreengraphics = this.createImage(this.getPreferredSize().width, this.getPreferredSize().height);
		appletgraphics = offscreengraphics.getGraphics();
		angles = new Angles(gamewindowx, gamewindowy);
		themap = new Map(this);
		graphics = new GameWindow(gamewindowx, gamewindowy, offscreengraphics, appletgraphics, angles, this, themap, useimages, hardcodedimages);
		engine = new Raycaster(angles, themap, graphics, useimages);
		player = new Player(angles, themap, usecollisiondetection, useimages);
		player.setRenderer(graphics);
		graphics.passPlayer(player);
		trigtables = new TrigTableBuilder(angles, engine, graphics);
	    trigtables.buildtrigtables();
	    engine.passtrigtables(player);
	    mainthread = new Thread(this);
	    mainthread.start(); 
	}
	
	@SuppressWarnings("static-access")
	public void gamemainloop() {
		while (playing) {
			themap.updateDoors();
			player.move(KeyLeft, KeyRight, KeyUp, KeyDown, KeyCtrl, KeyAlt, KeySpace, KeyShift);
			engine.castRays(player.getAngle(), player.getX(), player.getY());
			try {
				mainthread.sleep(sleeptime); 
			} 
			catch (InterruptedException e) {
				e.printStackTrace(); 
			}
		}
	}
	
	@Override
	public void paint(Graphics g) {
		g.drawImage(offscreengraphics, 0, 0, this);
	}
	
	public void update(Graphics g) {
		paint(g);
	}
	
	@Override
	public void stop() {
		if ((mainthread != null) && (mainthread.isAlive())) {
			playing = false;
			mainthread = null;
		}
	}

	@Override
	public void keyPressed(KeyEvent e) {
		switch (e.getKeyCode()) {
	    	case KeyEvent.VK_UP:
	    		KeyUp = true;
	    		break;
	        case KeyEvent.VK_DOWN:
	        	KeyDown = true;
	        	break;
	        case KeyEvent.VK_LEFT:
	        	KeyLeft = true;
	        	break;
	        case KeyEvent.VK_RIGHT:
	        	KeyRight = true;
	        	break;
	        case KeyEvent.VK_CONTROL:
	        	KeyCtrl = true;
	        	break;
	        case KeyEvent.VK_ALT:
	        	KeyAlt = true;
	        	break;
	        case KeyEvent.VK_SPACE:
	        	KeySpace = true;
	        	break;
	        case KeyEvent.VK_SHIFT:
	        	KeyShift = true;
	        default: 
	        	break;
		}
	}

	@Override
	public void keyReleased(KeyEvent e) {
		switch (e.getKeyCode()) {
	    	case KeyEvent.VK_UP:
	    		KeyUp = false;
	    		break;
	        case KeyEvent.VK_DOWN:
	        	KeyDown = false;
	        	break;
	        case KeyEvent.VK_LEFT:
	        	KeyLeft = false;
	        	break;
	        case KeyEvent.VK_RIGHT:
	        	KeyRight = false;
	        	break;
	        case KeyEvent.VK_CONTROL:
	        	KeyCtrl = false;
	        	break;
	        case KeyEvent.VK_ALT:
	        	KeyAlt = false;
	        	break;
	        case KeyEvent.VK_SPACE:
	        	KeySpace = false;
	        	break;
	        case KeyEvent.VK_SHIFT:
	        	KeyShift = false;
	        default: 
	        	break;
		}
	}

	@Override
	public void keyTyped(KeyEvent e) {
		// Should not do anything.
	}
}
